#include "Inf_ES7210.h"
#include "driver/i2c.h"
#include "driver/i2s_tdm.h"
#include "driver/sdmmc_host.h"
#include "es7210.h"
#include "esp_check.h"
#include "esp_vfs_fat.h"
#include "format_wav.h"
#include "sdkconfig.h"
#include <string.h>

/* I2C port and GPIOs */
#define EXAMPLE_I2C_NUM (0)
#define EXAMPLE_I2C_SDA_IO (1)
#define EXAMPLE_I2C_SCL_IO (2)

/* I2S port and GPIOs */
#define EXAMPLE_I2S_NUM (0)
#define EXAMPLE_I2S_MCK_IO (38)
#define EXAMPLE_I2S_BCK_IO (14)
#define EXAMPLE_I2S_WS_IO (13)
#define EXAMPLE_I2S_DI_IO (12)

/* SD card SPI GPIOs */
#define EXAMPLE_SD_SPI_CMD_IO (48)
#define EXAMPLE_SD_SPI_CLK_IO (47)
#define EXAMPLE_SD_SPI_DATA0_IO (21)

///// 我们只打开了2个麦克风
#define EXAMPLE_I2S_TDM_FORMAT (ES7210_I2S_FMT_I2S)
#define EXAMPLE_I2S_CHAN_NUM (2)
#define EXAMPLE_I2S_SAMPLE_RATE (48000)
#define EXAMPLE_I2S_MCLK_MULTIPLE (I2S_MCLK_MULTIPLE_256)
#define EXAMPLE_I2S_SAMPLE_BITS (I2S_DATA_BIT_WIDTH_16BIT)
#define EXAMPLE_I2S_TDM_SLOT_MASK (I2S_TDM_SLOT0 | I2S_TDM_SLOT1)

/* ES7210 configurations */
#define EXAMPLE_ES7210_I2C_ADDR (0x41)
#define EXAMPLE_ES7210_I2C_CLK (100000)
#define EXAMPLE_ES7210_MIC_GAIN (ES7210_MIC_GAIN_30DB)
#define EXAMPLE_ES7210_MIC_BIAS (ES7210_MIC_BIAS_2V87)
#define EXAMPLE_ES7210_ADC_VOLUME (0)

/* SD card & recording configurations */
#define EXAMPLE_RECORD_TIME_SEC (10)
// 挂载点
#define EXAMPLE_SD_MOUNT_POINT "/sdcard"
#define EXAMPLE_RECORD_FILE_PATH "/RECORD.WAV"

static const char *TAG = "example";

static i2s_chan_handle_t es7210_i2s_init(void) {
  i2s_chan_handle_t i2s_rx_chan = NULL;
  ESP_LOGI(TAG, "Create I2S receive channel");
  i2s_chan_config_t i2s_rx_conf = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_AUTO, I2S_ROLE_MASTER);
  ESP_ERROR_CHECK(i2s_new_channel(&i2s_rx_conf, NULL, &i2s_rx_chan));

  ESP_LOGI(TAG, "Configure I2S receive channel to TDM mode");
  i2s_tdm_config_t i2s_tdm_rx_conf = {
      .slot_cfg = I2S_TDM_PHILIPS_SLOT_DEFAULT_CONFIG(EXAMPLE_I2S_SAMPLE_BITS, I2S_SLOT_MODE_STEREO, EXAMPLE_I2S_TDM_SLOT_MASK),
      .clk_cfg = {.clk_src = I2S_CLK_SRC_DEFAULT, .sample_rate_hz = EXAMPLE_I2S_SAMPLE_RATE, .mclk_multiple = EXAMPLE_I2S_MCLK_MULTIPLE},
      .gpio_cfg = {.mclk = EXAMPLE_I2S_MCK_IO,
                   .bclk = EXAMPLE_I2S_BCK_IO,
                   .ws = EXAMPLE_I2S_WS_IO,
                   .dout = -1, // ES7210 only has ADC capability
                   .din = EXAMPLE_I2S_DI_IO},
  };

  ESP_ERROR_CHECK(i2s_channel_init_tdm_mode(i2s_rx_chan, &i2s_tdm_rx_conf));

  return i2s_rx_chan;
}

void mount_sdcard(sdmmc_card_t *card) {
  // 用于存储操作的返回状态
  esp_err_t ret;

  // 配置 SD 卡文件系统挂载参数
  // .format_if_mount_failed = true 表示挂载失败时尝试格式化 SD 卡
  // .max_files = 5 表示允许同时打开的最大文件数为 5
  // .allocation_unit_size = 16 * 1024 表示分配单元大小为 16KB
  esp_vfs_fat_sdmmc_mount_config_t mount_config = {.format_if_mount_failed = true, .max_files = 5, .allocation_unit_size = 16 * 1024};
  // 指向 SD 卡对象的指针

  // 记录信息日志，提示正在初始化 SD 卡
  ESP_LOGI(TAG, "Initializing SD card");

  // 记录信息日志，提示使用 SDMMC 外设
  ESP_LOGI(TAG, "Using SDMMC peripheral");

  // 初始化 SDMMC 主机配置为默认值
  sdmmc_host_t host = SDMMC_HOST_DEFAULT();

  // 初始化 SDMMC 插槽配置为默认值
  sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT();
  // 设置 SD 卡数据总线宽度为 1 位
  slot_config.width = 1;
  // 设置 SD 卡时钟线对应的引脚
  slot_config.clk = EXAMPLE_SD_SPI_CLK_IO;
  // 设置 SD 卡命令线对应的引脚
  slot_config.cmd = EXAMPLE_SD_SPI_CMD_IO;
  // 设置 SD 卡数据 0 线对应的引脚
  slot_config.d0 = EXAMPLE_SD_SPI_DATA0_IO;

  // 启用 SDMMC 插槽的内部上拉电阻
  slot_config.flags |= SDMMC_SLOT_FLAG_INTERNAL_PULLUP;

  // 记录信息日志，提示正在挂载文件系统
  ESP_LOGI(TAG, "Mounting filesystem");

  while (1) {
    // 尝试将 SD 卡挂载到指定的挂载点
    ret = esp_vfs_fat_sdmmc_mount(EXAMPLE_SD_MOUNT_POINT, &host, &slot_config, &mount_config, &card);
    if (ret == ESP_OK) {
      break;
    } else if (ret == ESP_FAIL) {
      ESP_LOGE(TAG, "Failed to mount filesystem.");
    } else {
      ESP_LOGE(TAG,
               "Failed to initialize the card (%s). "
               "Make sure SD card lines have pull-up resistors in place.",
               esp_err_to_name(ret));
    }
    vTaskDelay(pdMS_TO_TICKS(1000));
  }
}

static void es7210_codec_init(void) {
  ESP_LOGI(TAG, "Init I2C used to configure ES7210");
  i2c_config_t i2c_conf = {
      .sda_io_num = EXAMPLE_I2C_SDA_IO,
      .scl_io_num = EXAMPLE_I2C_SCL_IO,
      .mode = I2C_MODE_MASTER,
      .sda_pullup_en = GPIO_PULLUP_ENABLE,
      .scl_pullup_en = GPIO_PULLUP_ENABLE,
      .master.clk_speed = EXAMPLE_ES7210_I2C_CLK,
  };
  ESP_ERROR_CHECK(i2c_param_config(EXAMPLE_I2C_NUM, &i2c_conf));
  ESP_ERROR_CHECK(i2c_driver_install(EXAMPLE_I2C_NUM, i2c_conf.mode, 0, 0, 0));

  /* Create ES7210 device handle */
  es7210_dev_handle_t es7210_handle = NULL;
  es7210_i2c_config_t es7210_i2c_conf = {.i2c_port = EXAMPLE_I2C_NUM, .i2c_addr = EXAMPLE_ES7210_I2C_ADDR};
  ESP_ERROR_CHECK(es7210_new_codec(&es7210_i2c_conf, &es7210_handle));

  ESP_LOGI(TAG, "Configure ES7210 codec parameters");
  es7210_codec_config_t codec_conf = {.i2s_format = EXAMPLE_I2S_TDM_FORMAT,
                                      .mclk_ratio = EXAMPLE_I2S_MCLK_MULTIPLE,
                                      .sample_rate_hz = EXAMPLE_I2S_SAMPLE_RATE,
                                      .bit_width = (es7210_i2s_bits_t)EXAMPLE_I2S_SAMPLE_BITS,
                                      .mic_bias = EXAMPLE_ES7210_MIC_BIAS,
                                      .mic_gain = EXAMPLE_ES7210_MIC_GAIN,
                                      .flags.tdm_enable = true};
  ESP_ERROR_CHECK(es7210_config_codec(es7210_handle, &codec_conf));
  ESP_ERROR_CHECK(es7210_config_volume(es7210_handle, EXAMPLE_ES7210_ADC_VOLUME));
}

static esp_err_t record_wav(i2s_chan_handle_t i2s_rx_chan) {
  ESP_RETURN_ON_FALSE(i2s_rx_chan, ESP_FAIL, TAG, "invalid i2s channel handle pointer");
  esp_err_t ret = ESP_OK;

  uint32_t byte_rate = EXAMPLE_I2S_SAMPLE_RATE * EXAMPLE_I2S_CHAN_NUM * EXAMPLE_I2S_SAMPLE_BITS / 8;
  uint32_t wav_size = byte_rate * EXAMPLE_RECORD_TIME_SEC;

  const wav_header_t wav_header = WAV_HEADER_PCM_DEFAULT(wav_size, EXAMPLE_I2S_SAMPLE_BITS, EXAMPLE_I2S_SAMPLE_RATE, EXAMPLE_I2S_CHAN_NUM);

  ESP_LOGI(TAG, "Opening file %s", EXAMPLE_RECORD_FILE_PATH);
  FILE *f = fopen(EXAMPLE_SD_MOUNT_POINT EXAMPLE_RECORD_FILE_PATH, "w");
  ESP_RETURN_ON_FALSE(f, ESP_FAIL, TAG, "error while opening wav file");

  /* Write wav header */
  ESP_GOTO_ON_FALSE(fwrite(&wav_header, sizeof(wav_header_t), 1, f), ESP_FAIL, err, TAG, "error while writing wav header");

  /* Start recording */
  size_t wav_written = 0;
  static int16_t i2s_readraw_buff[4096];
  ESP_GOTO_ON_ERROR(i2s_channel_enable(i2s_rx_chan), err, TAG, "error while starting i2s rx channel");
  while (wav_written < wav_size) {
    if (wav_written % byte_rate < sizeof(i2s_readraw_buff)) {
      ESP_LOGI(TAG, "Recording: %" PRIu32 "/%ds", wav_written / byte_rate + 1, EXAMPLE_RECORD_TIME_SEC);
    }
    size_t bytes_read = 0;
    /* Read RAW samples from ES7210 */
    ESP_GOTO_ON_ERROR(i2s_channel_read(i2s_rx_chan, i2s_readraw_buff, sizeof(i2s_readraw_buff), &bytes_read, pdMS_TO_TICKS(1000)), err, TAG,
                      "error while reading samples from i2s");
    /* Write the samples to the WAV file */
    ESP_GOTO_ON_FALSE(fwrite(i2s_readraw_buff, bytes_read, 1, f), ESP_FAIL, err, TAG, "error while writing samples to wav file");
    wav_written += bytes_read;
  }

err:
  i2s_channel_disable(i2s_rx_chan);
  ESP_LOGI(TAG, "Recording done! Flushing file buffer");
  fclose(f);

  return ret;
}

/**
 * @brief 应用程序的主入口函数
 *
 * 此函数是整个应用程序的入口点，负责初始化相关硬件和外设，
 * 完成音频录制任务，并在录制结束后进行资源清理。具体步骤包括：
 * 1. 初始化 I2C 总线和 I2S 总线，用于配置 ES7210 并接收音频数据。
 * 2. 创建 ES7210 设备句柄并配置编解码器参数。
 * 3. 挂载 SD 卡，用于存储录制的音频文件。
 * 4. 开始录制 WAV 格式的音频文件。
 * 5. 卸载 SD 卡并输出录制结果信息。
 */
void Inf_ES7210_Init(void) {
  /* 初始化 I2C 总线以配置 ES7210，初始化 I2S 总线以从 ES7210 接收音频数据 */
  i2s_chan_handle_t i2s_rx_chan = es7210_i2s_init();
  /* 创建 ES7210 设备句柄并配置编解码器参数 */
  es7210_codec_init();
  /* 挂载 SD 卡，录制的音频文件将保存到该卡中 */
  sdmmc_card_t *sdmmc_card = NULL;
  mount_sdcard(sdmmc_card);
  /* 开始录制 WAV 格式的音频文件 */
  esp_err_t err = record_wav(i2s_rx_chan);
  /* 卸载 SD 卡 */
  esp_vfs_fat_sdcard_unmount(EXAMPLE_SD_MOUNT_POINT, sdmmc_card);
  // 检查录制是否成功
  if (err == ESP_OK) {
    // 录制成功，输出成功信息
    ESP_LOGI(TAG, "Audio was successfully recorded into " EXAMPLE_RECORD_FILE_PATH ". You can now remove the SD card safely");
  } else {
    // 录制失败，输出错误信息
    ESP_LOGE(TAG, "Record failed, " EXAMPLE_RECORD_FILE_PATH " on SD card may not be playable.");
  }
}